﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace NovoGenus.Sockets.Http
{

    /// <summary>
    /// Ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
    /// Http version 1.1
    /// </summary>
    public class HttpClient : IDisposable
    {
        private static uint BYTE_LIMIT = 1048576;
        private static int BUFFER_LENGTH = 512;
        private bool _headerFound = false;

        private StreamSocket _streamSocket;
        private string _port;
        private HostName _hostName;
        private bool _connected = false;

        public StreamSocketControl SocketControl { get { return _streamSocket.Control; } private set { } }
        public StreamSocketInformation SocketInformation { get { return _streamSocket.Information; } private set { } }

        public HttpRequest Request { get; set; }
        public HttpResponse Response { get; set; }

        public async Task<string> TestAsync()
        {
            Task<string> task = Task.Run(() => { return "in test async"; });
            string message = await task;
            return message;
        }
        

        public HttpClient(string host, string uri, string port = "80")
        {
            _streamSocket = new StreamSocket();
            _streamSocket.Control.KeepAlive = true;//default
            _streamSocket.Control.QualityOfService = SocketQualityOfService.Normal;//default   

            _hostName = new HostName(host);
            _port = port;

            Request = new HttpRequest(host, uri);
            Response = new HttpResponse();
        }

        ~HttpClient()
        {
            if (!ReferenceEquals(_streamSocket, null)) _streamSocket.Dispose();
            _streamSocket = null;
        }

        public async Task ConnectAsync()
        {
            try
            {
                await _streamSocket.ConnectAsync(_hostName, _port, SocketProtectionLevel.PlainSocket);//must wait here to make sure outputstream is ready before use
                DataWriter dw = new DataWriter(_streamSocket.OutputStream);
                dw.WriteBytes(Request.Headers.GetBytes());
                await dw.StoreAsync();
                await dw.FlushAsync();
                dw.DetachStream();
                _connected = true;
            }
            catch (Exception ex)
            {
                _connected = false;
            }
        }

        private bool EndLineFound(byte first, byte sec, byte third, byte fourth)
        {
            return first.Equals(10) && sec.Equals(13) && third.Equals(10) && fourth.Equals(13) ? true : false;
        }

        private void GetString(ref string dataRead, byte[] dataArray)
        {
            dataRead += UTF8Encoding.UTF8.GetString(dataArray, 0, dataArray.Length);
        }

        private void CopyHeaderData(out byte[] dest, ref byte[] source, int count)
        {
            dest = new byte[(count -= 2)];
            for (int i = 0; i < count; i++) dest[i] = source[i];
        }

        private void ResetDataContainer(out byte[] dataArray, out int index)
        {
            dataArray = new byte[BYTE_LIMIT]; index = 0;
        }

        private void ReadCopyData(ref byte[] buffer, ref byte[] dynamicArray, ref int arrayIndex, ref List<byte[]> dataList, ref int dataLength)
        {
            dataLength += buffer.Length;

            if ((arrayIndex + buffer.Length) > BYTE_LIMIT) //if limit reached then create new container
            {
                dataList.Add(dynamicArray);
                ResetDataContainer(out dynamicArray, out arrayIndex);
            }

            buffer.CopyTo(dynamicArray, arrayIndex);
            arrayIndex += buffer.Length;
        }


        public int ParseResponseData(byte[] buffer, uint unconsumed)
        {
            int bufferLengthToReadNext = !_headerFound? BUFFER_LENGTH:0;
            bool hexFound = false;
            int chunkedLength = 0;
            string splitter = Encoding.UTF8.GetString(new byte[]{5},0,1);
            string newLineReturn = "\r\n";

            string responseData = UTF8Encoding.UTF8.GetString(buffer, 0, buffer.Length);

            string[] data = null;

            if (!_headerFound)
            {
                responseData = responseData.Replace(newLineReturn, splitter);
                data = responseData.Split(splitter.ToCharArray()[0]);
            }
            else
            {
                if (Response.Headers.ContentLength > 0)
                {
                    int laps = buffer.Length - responseData.Length;
                    string temp = string.Empty;
                    for (int i = 0; i < laps; i++) temp = string.Concat(temp, "\n");
                    Response.Body = string.Concat(Response.Body,responseData, temp);
                    bufferLengthToReadNext = Response.Headers.ContentLength - Response.Body.Length;
                }
                else
                {
                    data = responseData.Split(new char[] { '\r', '\n' });
                }
            }

            for (int i = 0; data!=null && i < data.Length; i++)
            {
                if (!_headerFound)
                {
                    string[] responseLine = Response.Headers.GetHeaderResponseLine(data[i]);
                    if (string.IsNullOrEmpty(responseLine[0])) //end of header
                    {
                        _headerFound = true;
                        if (Response.Headers.ContentLength > 0 && (i + 1) < data.Length)
                        {
                            Response.Body = data[i+1];
                            bufferLengthToReadNext = Response.Headers.ContentLength - Response.Body.Length;
                            break;
                        }
                    }
                    Response.Headers.AddHeader(responseLine[0], responseLine[1]);
                }
                else
                {
                    if(Response.Headers.TransferEncoding.Equals("chunked"))
                    {
                         if (!hexFound && Int32.TryParse(data[i], System.Globalization.NumberStyles.AllowHexSpecifier, null, out chunkedLength))
                         {
                             hexFound = true;
                             bufferLengthToReadNext = chunkedLength;
                         }
                         else
                         {
                             Response.Body += string.Concat(data[i], "\n");
                         }
                    }

                }

            }


            return bufferLengthToReadNext;
        }

        public void completed()
        {

        }

        public async Task GetResponseAsync()
        {
            if (!_connected) throw new Exception("Not connected.");

            try
            {
                using (DataReader dataReader = new DataReader(_streamSocket.InputStream)) //get all data as fast from http server then close the connection; do work after.
                {
                    List<byte[]> dataList = new List<byte[]>();

                    dataReader.InputStreamOptions = InputStreamOptions.Partial;

                    //string dataread = string.Empty;

                    //int arrayIndex = 0;
                    byte[] buffer = null;
                    //byte[] dynamicArray = new byte[BYTE_LIMIT];
                    //bool keepReading = true;
                    //bool headerFound = false;
                    //int dataLength = 0;
                    //int headerEOL = 0;
                    int bufferLength = BUFFER_LENGTH;

                    while (bufferLength>0)
                    {
                        uint num = await dataReader.LoadAsync((uint)bufferLength);
                        Thread.Sleep(20);
                        if (num.Equals(0)) break;
                        buffer = new byte[num];//load or prepare to read bytes; create a buffer or a container                        
                        dataReader.ReadBytes(buffer);//read bytes and put in the container/buffer
                        bufferLength = ParseResponseData(buffer, dataReader.UnconsumedBufferLength);
                    };

                    dataReader.DetachStream();
                    dataReader.DetachBuffer();

                    if (dataList.Count > 0)
                    {
                        throw new NotSupportedException();
                    }
                }
            }
            catch (Exception ex)
            {
                _connected = false;
                Dispose();
            }

  
        }


        public void Dispose()
        {
            _streamSocket.Dispose();
            _streamSocket = null;
        }
    }
}

